package com.trainoo.alibaba.sentinel;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Auther: zt
 * @Date: 2019/12/13 10:07
 * @Description:
 */
@SpringBootApplication
public class SentinelApplication {

    public static void main(String[] args) {
        SpringApplication.run(SentinelApplication.class);
    }

    @Slf4j
    @RestController
    static class TestController{

        @Autowired
        TestService testService;

        @GetMapping("/test1")
        public String test1(){
            testService.doSomething("Hi");
            return "rate limit test";
        }

        @GetMapping("/test2")
        public String test2(){
            testService.doSomething2("Hello");
            return "this is test2";
        }
    }

    /**
     * blockHandler 函数访问范围需要是 public  |||
     */
    @Slf4j
    @Service
    public static class TestService{

        /**
         * SentinelResource注解： 在需要通过Sentinel来控制流量的地方使用，加上之后可以在dashboard簇点链路中观察到
         *
         * default value = com.trainoo.alibaba.sentinel.SentinelApplication$TestService:doSomethine(java.lang.String)
         *
         */
        @SentinelResource(value="doSomething", blockHandler="exceptionHandler")
        public void doSomething(String hello){
            log.info("{}. i'm doing someting", hello);
        }

        /**
         ** 限流与阻塞处理 **
         *  如果不做处理的话，前端看到如下错误信息：
         *  {
         *     "timestamp": "2019-12-13T06:14:57.754+0000",
         *     "status": 500,
         *     "error": "Internal Server Error",
         *     "message": "No message available",
         *     "path": "/hello"
         *  }
         *
         *  如果加上了，前端会正常返回: "Hello world~~"
         *
         *  - blockHandler 函数访问范围需要是 public，
         *  - 返回类型需要与原方法相匹配，
         *  - 参数类型需要和原方法相匹配并且最后加一个额外的参数，类型为 BlockException。
         *  - blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数，则可以指定 blockHandlerClass 为对应的类的 Class 对象，注意对应的函数必需为 static 函数，否则无法解析。
         */
        public void exceptionHandler(String str, BlockException ex) {
            log.error("被限流惹~~：" + str, ex);
        }

        /**
         * 熔断与降级
         * 1. 使用异常比例的方式： 前5个请求会被作为判断异常比例的依据，从第6个请求开始判断是否符合降级规则
         * 2. 异常数 (DEGRADE_GRADE_EXCEPTION_COUNT)：当资源近 1 分钟的异常数目超过阈值之后会进行熔断。
         *    注意由于统计时间窗口是分钟级别的，若 timeWindow 小于 60s，则结束熔断状态后仍可能再进入熔断状态。
         * 3. 平均响应时间 (DEGRADE_GRADE_RT)：当 1s 内持续进入 5 个请求，对应时刻的平均响应时间（秒级）均超过阈值（count，以 ms 为单位），
         *    那么在接下的时间窗口（DegradeRule 中的 timeWindow，以 s 为单位）之内，对这个方法的调用都会自动地熔断（抛出 DegradeException）。
         *    注意 Sentinel 默认统计的 RT 上限是 4900 ms，超出此阈值的都会算作 4900 ms，若需要变更此上限可以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx 来配置。
         */
        @SentinelResource(value="doSomething222", fallback="fallbackHandler")
        public void doSomething2(String hello){
            log.info("{}. i'm doing someting 22222", hello);
            throw new RuntimeException("故意抛出的异常~~");
        }

        /**
         * fallback 函数签名和位置要求：
         * - 返回值类型必须与原函数返回值类型一致；
         * - 方法参数列表需要和原函数一致，或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
         * - fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数，则可以指定 fallbackClass 为对应的类的 Class 对象，注意对应的函数必需为 static 函数，否则无法解析。
         */
        public void fallbackHandler(String str) {
            log.error("被降级惹~~：" + str);
        }

        /**
         * 特别地，若 blockHandler 和 fallback 都进行了配置，则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。
         * 若未配置 blockHandler、fallback 和 defaultFallback，则被限流降级时会将 BlockException 直接抛出。
         */
        public void nomean(){}
    }
}
